/*
 * @Author: lyx 2858535420@qq.com
 * @Date: 2022-10-10 22:31:07
 * @LastEditors: lyx 2858535420@qq.com
 * @LastEditTime: 2023-03-07 16:22:19
 * @FilePath: /antd-vue2-template/src/utils/index.js
 * @Description: 公用方法
 */

import CryptoJS from 'crypto-js';
import JSEncrypt from 'jsencrypt';

export function timeFix() {
  const time = new Date();
  const hour = time.getHours();
  return hour < 9 ? '早上好' : hour <= 11 ? '上午好' : hour <= 13 ? '中午好' : hour < 20 ? '下午好' : '晚上好';
}

export function welcome() {
  const arr = ['休息一会儿吧', '准备吃什么呢?', '要不要打一把 DOTA', '我猜你可能累了'];
  const index = Math.floor(Math.random() * arr.length);
  return arr[index];
}

/**
 * 触发 window.resize
 */
export function triggerWindowResizeEvent() {
  const event = document.createEvent('HTMLEvents');
  event.initEvent('resize', true, true);
  event.eventType = 'message';
  window.dispatchEvent(event);
}

export function handleScrollHeader(callback) {
  let timer = 0;

  let beforeScrollTop = window.pageYOffset;
  callback = callback || function () { };
  window.addEventListener(
    'scroll',
    event => {
      clearTimeout(timer);
      timer = setTimeout(() => {
        let direction = 'up';
        const afterScrollTop = window.pageYOffset;
        const delta = afterScrollTop - beforeScrollTop;
        if (delta === 0) {
          return false;
        }
        direction = delta > 0 ? 'down' : 'up';
        callback(direction);
        beforeScrollTop = afterScrollTop;
      }, 50);
    },
    false
  );
}

export function isIE() {
  const bw = window.navigator.userAgent;
  const compare = (s) => bw.indexOf(s) >= 0;
  const ie11 = (() => 'ActiveXObject' in window)();
  return compare('MSIE') || ie11;
}

/**
 * Remove loading animate
 * @param id parent element id or class
 * @param timeout
 */
export function removeLoadingAnimate(id = '', timeout = 1500) {
  if (id === '') {
    return;
  }
  setTimeout(() => {
    document.body.removeChild(document.getElementById(id));
  }, timeout);
}

export function scorePassword(pass) {
  let score = 0;
  if (!pass) {
    return score;
  }
  // award every unique letter until 5 repetitions
  const letters = {};
  for (let i = 0; i < pass.length; i++) {
    letters[pass[i]] = (letters[pass[i]] || 0) + 1;
    score += 5.0 / letters[pass[i]];
  }

  // bonus points for mixing it up
  const variations = {
    digits: /\d/.test(pass),
    lower: /[a-z]/.test(pass),
    upper: /[A-Z]/.test(pass),
    nonWords: /\W/.test(pass)
  };

  let variationCount = 0;
  for (var check in variations) {
    variationCount += (variations[check] === true) ? 1 : 0;
  }
  score += (variationCount - 1) * 10;

  return parseInt(score);
}

// 身份 card 类型
export const CARD_TYPE = {
  NI: '身份证',
  PP: '护照',
  OT: '其他',
  BC: '出生证明',
  HR: '户口簿',
  TB: '台胞证',
  HX: '回乡证',
  HM: '港澳通行证',
  FI: '外国人永久居住证',
  JR: '军人证'
};

// 请求头 content-type 类型
export const ENUM_CONTENT_TYPE = {
  // 表单
  FORM_TYPE: 'application/x-www-form-urlencoded;charset=utf-8',
  // json 数据
  JSON_TYPE: 'application/json;charset=utf-8',
  // file 文件上传
  FILE_TYPE: 'multipart/form-data;charset=utf-8'
};

// 性别 对象
export const ENUM_SEX_TYPE = [{
  name: '男',
  value: 1
}, {
  name: '女',
  value: 0
}];

// 常量工具类
export const constUitls = {

  // 周中文描述
  WEEK_ARR: ['日', '一', '二', '三', '四', '五', '六'],

  // 星期中文速查表 ['周日','周一','周二','周三','周四','周五','周六']
  WEEK_UNI_CODE: ['\u5468\u65e5', '\u5468\u4e00', '\u5468\u4e8c', '\u5468\u4e09', '\u5468\u56db', '\u5468\u4e94', '\u5468\u516d'],

  // 四季 春季/夏季/秋季/冬季
  FOUR_SEASONS: ['春季', '夏季', '秋季', '冬季'],

  // 月份
  MONTH_ARR: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
};

// 颜色相关代码
export const colorUtils = {

  /**
   * 判断是否 十六进制颜色值.
   * 输入形式可为 #fff000 #f00
   *
   * @param   {string}  color   十六进制颜色值
   * @return  Boolean
   */
  isHexColor(color) {
    return !!color && /^#([0-9a-fA-F]{3}|[0-9a-fA-f]{6})$/.test(color);
  },

  /**
   * 16进制转换rgb
   * 
   * Transform a HEX color to its RGB representation
   * @param {string} hex The color to transform
   * @returns The RGB representation of the passed color
   */
  hexToRGB(hex) {
    if (!objectUtils.isString(hex) || !this.isHexColor(hex)) return '';
    let sHex = hex.toLowerCase();

    if (sHex.length === 4) {
      let sColorNew = '#';
      for (let i = 1; i < 4; i += 1) {
        sColorNew += sHex.slice(i, i + 1).concat(sHex.slice(i, i + 1));
      }
      sHex = sColorNew;
    }

    const sColorChange = [];
    for (let i = 1; i < 7; i += 2) {
      sColorChange.push(parseInt('0x' + sHex.slice(i, i + 2)));
    }

    return 'RGB(' + sColorChange.join(',') + ')';
  },

  /**
  * @description: 16进制转换rgb
  * @param {string} color 16进制的颜色值
  * @return {*}
  */
  hexToRGB1(color) {
    if (objectUtils.isEmpty(color) || !objectUtils.isString(color)) {
      console.error('请输入16进制字符串形式的颜色值');
      return '';
    }

    color = color.startsWith('#') ? color.substring(1) : color;
    const len = color.length;

    if (len !== 6 && len !== 3) {
      console.error('请输入正确的颜色值');
      return '';
    }

    if (len === 3) {
      color = color.replace(/(\w)(\w)(\w)/, '$1$1$2$2$3$3');
    }

    const reg = /\w{2}/g,
      colors = color.match(reg);

    for (let i = 0; i < colors.length; i++) {
      colors[i] = parseInt(colors[i], 16).toString();
    }

    return 'rgb(' + colors.join() + ')';
  },

  /**
  * RGB 颜色值转换为 十六进制颜色值.
  * r, g, 和 b 需要在 [0, 255] 范围内
  *
  * @return  String          类似#ff00ff
  * @param r  
  * @param g  
  * @param b 
  */
  rgbToHex(r, g, b) {
    const hex = ((r << 16) | (g << 8) | b).toString(16);
    return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex;
  },

  /**
   * @description: rgb颜色值转为为16进制的颜色值
   * @param {string} color  rgb 颜色
   * @return {*}
   */
  rgbToHex1(color) {
    if (!objectUtils.isString(color) || !color.startsWith('rgb')) {
      console.error('请输入rgb形式的颜色值');
      return '';
    }

    color = color.replace(/\s+/g, '');
    const index = color.indexOf('(') + 1;
    // 注意： String 的slice方法，slice方法参数为负数时，即为倒数
    // substring 方法参数为负数时，全都认为是0
    const colors = color.slice(index, -1).split(',').slice(0, 3);

    for (let i = 0; i < colors.length; i++) {
      let temp = parseInt(colors[i], 10);
      if (temp > 255 || temp < 0) {
        console.error('颜色值范围在0到255之间，请注意输入值！');
        return '';
      }

      colors[i] = temp.toString(16).padStart(2, '0');
    }
    return `#${colors.join('')}`;
  },

  /* Suma el porcentaje indicado a un color (RR, GG o BB) hexadecimal para aclararlo */
  /**
   * Sums the passed percentage to the R, G or B of a HEX color
   * 
   * @param {string} color The color to change
   * @param {number} amount The amount to change the color by
   * @returns {string} The processed part of the color
   */
  addLight(color, amount) {
    const cc = Number.parseInt(color, 16) + amount,
      c = cc > 255 ? 255 : cc;

    return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`;
  },

  /**
   * Calculates luminance of an rgb color
   * 
   * @param {number} r red
   * @param {number} g green
   * @param {number} b blue
   */
  luminanace(r, g, b) {
    const a = [r, g, b].map((v) => {
      v /= 255;
      return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
    });

    return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
  },

  /**
   * Calculates contrast between two rgb colors
   * 计算两种rgb颜色之间的对比度
   * 
   * @param {string} rgb1 rgb color 1
   * @param {string} rgb2 rgb color 2
   */
  contrast: (rgb1, rgb2) => (
    (this.luminanace(~~rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) /
    (this.luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05)
  ),

  /**
   * Subtracts the indicated percentage to the R, G or B of a HEX color
   * 
   * @param {string} color The color to change
   * @param {number} amount The amount to change the color by
   * @returns {string} The processed part of the color
   */
  subtractLight(color, amount) {
    const cc = parseInt(color, 16) - amount,
      c = cc < 0 ? 0 : cc;

    return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`;
  },

  /**
   * @description: 将颜色值扩大或者缩小一个背书
   * @param {string} color     颜色值 例如 #999 #999999 rgb(255,255,255)
   * @param {number} scale     扩大或者缩小的倍数 0-1
   * @param {boolean} isDark   扩大还是缩小  true 表示缩小
   * @return {*} 返回 #999 扩大或者缩小后的颜色值
   */
  colorExpandScale(color, scale, isDark) {
    if (objectUtils.isEmpty(color) || !objectUtils.isString(color)) return '';
    if (scale < 0 || scale > 1) return '';
    color = color.startsWith('#') ? this.hexToRGB1(color) : color;

    // 替换所有的空格
    color = color.replace(/\s+/g, '');
    let index = color.indexOf('(') + 1,
      colors = color.slice(index, -1).split(',').slice(0, 3);

    for (let i = 0, len = colors.length; i < len; i++) {
      let temp = parseInt(colors[i], 10);
      if (temp > 255 || temp < 0) return '';
      temp = isDark ? temp * scale : temp + (255 - temp) * scale;

      colors[i] = temp.toString(16).padStart(2, '0');
    }

    return `#${colors.join('')}`;
  },

  /**
   * @description: 颜色是否是黑色的
   * @param {string} color
   * @return {*}
   */
  colorIsDark(color) {
    if (!this.isHexColor(color)) return false;
    const [r, g, b] = this.hexToRGB(color)
      .replace(/(?:\(|\)|rgb|RGB)*/g, '')
      .split(',')
      .map((item) => Number(item));

    return r * 0.299 + g * 0.578 + b * 0.114 < 192;
  },

  /**
   * Darkens a HEX color given the passed percentage
   * @param {string} color The color to process
   * @param {number} amount The amount to change the color by
   * @returns {string} The HEX representation of the processed color
   */
  darken(color, amount) {
    color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color;
    amount = Math.trunc((255 * amount) / 100);

    return `#${this.subtractLight(color.substring(0, 2), amount)}${this.subtractLight(
      color.substring(2, 4),
      amount
    )}${this.subtractLight(color.substring(4, 6), amount)}`;
  },

  /**
   * Lightens a 6 char HEX color according to the passed percentage
   * @param {string} color The color to change
   * @param {number} amount The amount to change the color by
   * @returns {string} The processed color represented as HEX
   */
  lighten(color, amount) {
    color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color;
    amount = Math.trunc((255 * amount) / 100);

    return `#${this.addLight(color.substring(0, 2), amount)}${this.addLight(
      color.substring(2, 4),
      amount
    )}${this.addLight(color.substring(4, 6), amount)}`;
  },

  /**
    * Determines what the best text color is (black or white) based con the contrast with the background
    * @param hexColor - Last selected color by the user
    */
  calculateBestTextColor(hexColor) {
    const rgbColor = this.hexToRGB(hexColor.substring(1));
    const contrastWithBlack = this.contrast(rgbColor.split(','), [0, 0, 0]);

    return contrastWithBlack >= 12 ? '#000000' : '#FFFFFF';
  }
};

// 数据类型转换
export const convertUtils = {

  /**
   * 转换成 整型 number
   * 
   * 如果是 null/undefine 则返回 0
   * 
   * @param  { any } val 
   * @returns 
   */
  toNumberInt(val, def = 0) {
    return !numberUtils.isInteger(val) ? def : val * 1;
  },

  /**
   * 转换成 浮点数 number
   * 
   * 如果是 null/undefine 则返回 0
   * 
   * @param  { any } val 
   * @returns 
   */
  toNumberFloat(val, def = 0) {
    return !numberUtils.isFloat(val) ? def : val * 1;
  },

  /**
   * @description: 将小数转成整数, 多余的小数将直接抛弃
   * @param {any} num   需要转换的小数
   * @param {number} digits  保留几位小数，默认2位
   * @return {*}
   */
  toInterger(num, digits = 2) {
    if (num == null) return 0;

    let isPlus = num >= 0,
      [inte, deci] = num.toString().split('.'),
      powNum = Math.pow(10, digits);

    deci = numberUtils.fixZeroEnd(deci, digits);
    return (Math.abs(inte * powNum) + deci * 1) * (isPlus ? 1 : -1);
  },

  /**
   * 转换成 string
   * 
   * 如果是 null/undefine 则返回 ""
   * 
   * @param {any} val 
   * @returns 
   */
  toString(val, def = '') {
    if (val == null) return def;
    if (objectUtils.isString(val)) return val;

    return JSON.stringify(val);
  },

  /**
   * @description: 转换成数组
   * @param {any} val
   * @return {*}
   */
  toArray(val, def = []) {
    if (val == null || objectUtils.isObject(val)) return def;
    if (objectUtils.isArray(val)) return val;

    let ret;
    try {
      ret = JSON.parse(val);
    } catch {
      ret = def;
    }

    return objectUtils.isArray(ret) ? ret : def;
  },

  /**
   * @description: 转换成对象 只能是 {} 类的对象
   * @param {any} val
   * @return {*}
   */
  toObject(val, def = {}) {
    if (val == null || objectUtils.isArray(val)) return def;
    if (objectUtils.isObject(val)) return val;

    let ret;
    try {
      ret = JSON.parse(val);
    } catch {
      ret = def;
    }
    return objectUtils.isObject(ret) ? ret : def;
  }
};

// 加密公用方法
export const cryptoUtils = {

  // 加密/解密 key
  CRYPTO_KEY: 'abc123abc',

  // 加密的 option
  CRYPTO_OPTION: {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
  },

  /**
   * @description: 对象加密
   * @param {any} data 加密的对象
   * @return {*}
   */
  encryptAES(data, key = cryptoUtils.CRYPTO_KEY) {
    if (objectUtils.isNullOrUnDef(data)) return '';

    return CryptoJS.AES.encrypt(convertUtils.toString(data), key, this.CRYPTO_OPTION).toString();
  },

  /**
   * @description: 解密
   * @param {string} val  需要解密的字符串
   * @return {*}
   */
  decryptAES(val, key = cryptoUtils.CRYPTO_KEY) {
    if (objectUtils.isNullOrUnDef(val)) return '';
    let ret = CryptoJS.AES.decrypt(val, key, this.CRYPTO_OPTION).toString(CryptoJS.enc.Utf8);

    return objectUtils.isEmpty(ret) ? ret : JSON.parse(ret);
  },

  /**
   * @description: 使用  DES 对字符串进行加密
   * @param {any} data
   * @param {string} key
   * @return {*}
   */
  encryptDES(data, key = cryptoUtils.CRYPTO_KEY) {
    if (objectUtils.isNullOrUnDef(data)) return '';

    return CryptoJS.DES.encrypt(convertUtils.toString(data), CryptoJS.enc.Utf8.parse(key), this.CRYPTO_OPTION).toString();
  },

  /**
   * @description: 使用  DES 对字符串进行加密
   * @param {string} val
   * @param {string} key
   * @return {*}
   */
  decryptDES(val, key = cryptoUtils.CRYPTO_KEY) {
    if (objectUtils.isNullOrUnDef(val)) return '';
    let ret = CryptoJS.DES.decrypt(val, CryptoJS.enc.Utf8.parse(key), this.CRYPTO_OPTION).toString(CryptoJS.enc.Utf8);

    return objectUtils.isEmpty(ret) ? ret : JSON.parse(ret);
  },

  /**
   * @description: rsa 加密
   * @param {string} val
   * @param {string} key
   * @return {*}
   */
  encryptRSA(val, key = cryptoUtils.CRYPTO_KEY) {
    if (objectUtils.isNullOrUnDef(val)) return '';

    let encrypt = new JSEncrypt();
    encrypt.setPublicKey(key);
    return encrypt.encrypt(convertUtils.toString(val));
  },

  /**
   * @description: rsa 解密
   * @param {string} val
   * @param {string} key
   * @return {*}
   */
  decryptRSA(val, key = cryptoUtils.CRYPTO_KEY) {
    if (objectUtils.isNullOrUnDef(val)) return '';

    let encrypt = new JSEncrypt();
    encrypt.setPrivateKey(key);
    return encrypt.decrypt(val);
  },

  /**
   * @description: 对字符串加密成md5
   * @param {string} data 需要加密的对象
   * @return {*}
   */
  encryptByMd5(data, key = cryptoUtils.CRYPTO_KEY) {
    if (objectUtils.isNullOrUnDef(data)) return '';

    return CryptoJS.MD5(`${key}${convertUtils.toString(data)}`).toString();
  },

  /**
   * @description: 加密成 base64
   * @param {any} data 
   * @return {*}
   */
  encryptByBase64(data) {
    if (objectUtils.isNullOrUnDef(data)) return '';

    return CryptoJS.enc.Utf8.parse(convertUtils.toString(data)).toString(CryptoJS.enc.Base64);
  },

  /**
   * @description: 解密 base64
   * @param {string} val
   * @return {*}
   */
  decodeByBase64(val) {
    if (objectUtils.isNullOrUnDef(val)) return '';
    let ret = CryptoJS.enc.Base64.parse(val).toString(CryptoJS.enc.Utf8);

    return objectUtils.isEmpty(ret) ? ret : JSON.parse(ret);
  }
};

// 时间帮助方法, 可以用 moment/dayjs
export const dateUtils = {

  /**
   * @description: 是否是时间对象
   * @param {any} val
   * @return {*}
   */
  isDate(val) {
    return val && objectUtils.is(val, 'Date');
  },

  /**
   * @description:  今天/现在
   * @param {*}
   * @return {Date}
   */
  now() {
    return new Date();
  },

  /**
   * @description: 前天
   * @param {*}
   * @return {*}
   */
  beforeYesterday() {
    return this.addTimeDiffDays(-2);
  },

  /**
   * @description: 昨天
   * @param {*}
   * @return {*}
   */
  yesterday() {
    return this.addTimeDiffDays(-1);
  },

  /**
   * @description: 明天
   * @param {*}
   * @return {*}
   */
  tomorrow() {
    return this.addTimeDiffDays(1);
  },

  /**
   * @description: 后天
   * @param {*}
   * @return {*}
   */
  afterTomorrow() {
    return this.addTimeDiffDays(2);
  },

  /**
   * @description:  获取系统当前时间, 返回 1970 年 1 月 1 日至今的毫秒数。
   * @param {*}
   * @return {number} 毫秒数
   */
  currentTimeMillis() {
    return this.now().getTime();
  },

  /**
   * @description:  格式化时间
   * @param {object|string} date 需要格式化的日期  
   * @param {string} fmt  格式化后的样子，默认 yyyy-MM-dd hh:mm:ss
   * @return {string}  格式化后需要的日期字符串
   */
  formatDate(date, fmt = 'yyyy-MM-dd hh:mm:ss') {
    if (objectUtils.isEmpty(date)) {
      console.log('格式化的时间不能为空!');
      return date;
    }

    if (objectUtils.isString(date)) date = this.parseDate(date);

    if (/(y+)/.test(fmt)) {
      fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
    }

    let o = {
      'M+': date.getMonth() + 1,
      'd+': date.getDate(),
      'h+': date.getHours(),
      'm+': date.getMinutes(),
      's+': date.getSeconds()
    };

    for (let k in o) {
      if (new RegExp(`(${k})`).test(fmt)) {
        let str = o[k] + '';
        fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : numberUtils.fixZeroStart(str));
      }
    }
    return fmt;
  },

  /**
   * @description: 将"yyyy-mm-dd HH:MM:ss  / yyyy-mm-dd " 格式的字符串，转化为一个Date对象
   * @param {string} date
   * @return {Date}
   */
  parseDate(date) {
    if (objectUtils.isEmpty(date)) return this.now();
    if (!objectUtils.isString(date)) return date;

    // todo: 新的实例化方式
    let a = date.split(/[^0-9]/);
    return new Date(a[0], (a[1] || 1) - 1, a[2] || 0, a[3] || 0, a[4] || 0, a[5] || 0);
  },

  /**
   * @description: 判断是否是闰年
   * @param {number} Year
   * @return {*}
   */
  isLeapYear(year) {
    if (!numberUtils.isParseNumber(year)) return false;

    return (((year % 4) === 0) && ((year % 100) !== 0)) || ((year % 400) === 0);
  },

  /**
   * @description: 在某个时间增加一个天数
   * @param { number } days   增加的天数，正数为增加，负数为减少天数
   * @param { Date | string } date      增加天数的目标时间，默认是当前时间
   * @return { Date } 返回增加时间后的日期
   */
  addTimeDiffDays(days = 0, date = '') {
    if (objectUtils.isEmpty(date)) date = this.now();
    if (objectUtils.isString(date)) date = this.parseDate(date);

    return new Date(date.getFullYear(),
      date.getMonth(),
      date.getDate() + days);
  },

  /**
   * @description: 在某个时间增加一个分钟数
   * @param { number } minutes         增加的分钟数，正数为增加，负数为减少
   * @param { Date | string } date   增加分钟数的目标时间，默认是当前时间
   * @return { Date } 返回增加时间后的日期
   */
  addTimeDiffMinutes(minutes = 0, date = '') {
    if (objectUtils.isEmpty(date)) date = this.now();
    if (objectUtils.isString(date)) date = this.parseDate(date);

    return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes() + minutes, date.getSeconds());
  },

  /**
   * @description: 获取今天是周几的中文字符
   * @param { Date | string } date 今天时间
   * @return {string} 返回周几， 例如：周一
   */
  getWeekChinese(date) {
    if (objectUtils.isEmpty(date)) return '';
    if (objectUtils.isString(date)) date = this.parseDate(date);

    return constUitls.WEEK_UNI_CODE[date.getDay()] || '';
  },

  /**
   * @description: 获取月份的中文
   * @param {any} date 获取月份的时间
   * @return {*} 返回月份  例如： 一月
   */
  getMonthChinese(date) {
    if (objectUtils.isEmpty(date)) return '';
    if (objectUtils.isString(date)) date = this.parseDate(date);

    return constUitls.MONTH_ARR[date.getMonth()] || '';
  },

  /**
   * @description: 获取四季的中文
   * @param {any} date 获取月份的时间
   * @return {*} 返回月份  例如：春季
   */
  getFourSeasonsChinese(date) {
    if (objectUtils.isEmpty(date)) return '';
    if (objectUtils.isString(date)) date = this.parseDate(date);

    let curMonth = date.getMonth(),
      foMonth = Math.floor(curMonth / 4);

    return constUitls.FOUR_SEASONS[foMonth] || '';
  },

  /**
   * @description: 获取时间段的中文描述 
   * [凌晨, 早上, 上午, 中午, 下午, 傍晚, 晚上, 夜里]
   * 
   * @param {any} date 时间, 如果为空则返回当前时间
   * @return {*}
   */
  formatTimeBucket(date) {
    if (objectUtils.isEmpty(date)) return '';
    if (objectUtils.isString(date)) date = this.parseDate(date);

    let hour = date.getHours();

    if (hour < 6) return '凌晨';
    else if (hour < 9) return '早上';
    else if (hour < 12) return '上午';
    else if (hour < 14) return '中午';
    else if (hour < 17) return '下午';
    else if (hour < 19) return '傍晚';
    else if (hour < 22) return '晚上';
    else return '夜里';
  },

  /**
   * @description: 将时间转换为 `几秒前`、`几分钟前`、`几小时前`、`几天前`
   * @param {date|string} date 需要转换的时间, 如果为空就是当前时间
   * 
   * 1, 时间类型  2，yyyy-mm-dd HH:MM:ss  / yyyy-mm-dd 格式的时间字符串
   * 
   * @return {*} 返回时间的描述
   */
  formatPast(date) {
    let time = this.getTimeDiffSeconds(this.now(), date);

    // 10秒内
    if (time < 10) return '刚刚';
    // 1分钟内
    if (time < 60) return `${time}秒前`;
    // 一个小时内
    if (time < 3600) return `${Math.floor(time / 60)}分钟前`;
    // 一天内
    if (time < 86400) return `${Math.floor(time / 3600)}小时前`;
    // 7天内
    if (time < 86400 * 7) return `${Math.floor(time / 86400)}天前`;
    // 一个月内
    if (time < 86400 * 30) return `一月前`;
    // 一年内
    if (time < 86400 * 365) return `一年前`;

    return '超过一年';
  },

  /**
   * @description: 获取两个时间的差 天
   * @param { Date | string } startDate 开始时间，如果为空则为当前时间
   * @param { Date | string } endDate   结束时间
   * @return {*}
   */
  getTimeDiffDays(endDate, startDate = '') {
    let mill = this.getTimeDiffMillisecond(endDate, startDate);

    return Math.floor(mill / 86400000);
  },

  /**
   * @description: 获取两个时间的差 分钟
   * @param { Date | string } startDate 开始时间，如果为空则为当前时间
   * @param { Date | string } endDate   结束时间
   * @return {*}
   */
  getTimeDiffMinutes(endDate, startDate = '') {
    let mill = this.getTimeDiffMillisecond(endDate, startDate);

    return Math.floor(mill / 60000);
  },

  /**
   * @description: 获取两个时间的差 秒
   * @param { Date | string } startDate 开始时间，如果为空则为当前时间
   * @param { Date | string } endDate   结束时间
   * @return {*}
   */
  getTimeDiffSeconds(endDate, startDate = '') {
    let mill = this.getTimeDiffMillisecond(endDate, startDate);

    return Math.floor(mill / 1000);
  },

  /**
   * @description: 获取两个时间的差 毫秒
   * @param { Date | string } startDate 开始时间，如果为空则为当前时间
   * @param { Date | string } endDate   结束时间
   * @return {*}
   */
  getTimeDiffMillisecond(endDate, startDate = '') {
    if (objectUtils.isEmpty(endDate)) {
      console.log('结束时间不能为空!');
      return 0;
    }

    if (objectUtils.isEmpty(startDate)) startDate = this.now();
    else if (objectUtils.isString(startDate)) startDate = this.parseDate(startDate);
    if (objectUtils.isString(endDate)) endDate = this.parseDate(endDate);

    return endDate.getTime() - startDate.getTime();
  },

  /**
   * @description: 获取两个时间之间所有的日期
   * @param {Date | string} endDate    结束时间
   * @param {Date | string} startDate  开始时间，如果为空则为当前时间
   * @return { Array<Date> }
   */
  getTimeDiffDate(endDate, startDate = '') {
    if (objectUtils.isEmpty(endDate)) return [];

    let days = this.getTimeDiffDays(endDate, startDate);
    if (days <= 0) return [];

    let arr = [];
    for (let i = 0; i < days; i++) {
      arr.push(this.addTimeDiffDays(i, startDate));
    }

    return arr;
  },

  /**
   * @description: 判断两个时间的大小，date1 大返回 整数 ，date2 大返回 负数 相当返回0
   * @param {Date | string} date1
   * @param {Date | string} date2
   * @return { number } 
   */
  compareDate(date1, date2) {
    if (objectUtils.isEmpty(date1)) return -1;
    if (objectUtils.isEmpty(date2)) return 1;

    return this.getTimeDiffMillisecond(date2, date1);
  }
};

// 数字 辅助类
export const numberUtils = {

  /**
   * 是否是一个 number 数据类型 并且有限
   * 
   * @param {any} val
   * @returns {boolean} true 是数字， false 不是数字
   * 
   */
  isNumber(val) {
    return typeof val === 'number' && Number.isFinite(val);
  },

  /**
   * 能够转成 number 数据类型并且有限
   * 
   * @param { string|number} val 
   */
  isParseNumber(val) {
    if (objectUtils.isEmpty(val) || !(this.isNumber(val) || objectUtils.isString(val))) return false;

    return !Number.isNaN(Number.parseFloat(val));
  },

  /**
   * 验证是否是整数
   * 
   * @param {any} val 可以是正/负整数
   */
  isInteger(val) {
    return this.isParseNumber(val) && Number.isInteger(val * 1);
  },

  /**
   * 验证是否是浮点数
   * 
   * @param {any} val 需要进行判断的值
   */
  isFloat(val) {
    return this.isParseNumber(val) && /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(val);
  },

  /**
   * @description: 是奇数
   * @param {number} num
   * @return {*}
   */
  isOddNumber(num) {
    return this.isInteger(num) && num % 2 > 0;
  },

  /**
   * @description: 偶数
   * @param {number} num
   * @return {*}
   */
  isEvenNumber(num) {
    return this.isInteger(num) && num % 2 <= 0;
  },

  /**
   * @description: 去掉小数
   * @param {number} num
   * @return {*}
   */
  toFloorInt(num) {
    return this.isParseNumber(num) && (num >= 0 ? Math.floor(num) : Math.ceil(num)) || num;
  },

  /**
   * @description: 保留小数, 会四舍五入, 会强制补零， 默认保留2位小数
   * @param {number|string} num 保留小数的数字
   * @param { number } digits   小数后几位，默认2位
   * @return {*}
   */
  toDecimal(num, digits = 2) {
    if (!this.isParseNumber(num)) return 0;
    let retNum = (Math.round(num * Math.pow(10, digits)) / Math.pow(10, digits)).toFixed(digits);

    return retNum * 1;
  },

  /**
   * @description: 保留小数, 不会四舍五入, 不会强制补零， 默认保留2位小数
   * @param {number|string} num 保留小数的数字
   * @param { number } digits   小数后几位，默认2位
   * @return {*}
   */
  toDecimal1(num, digits = 2) {
    return this.isParseNumber(num) && Math.round(num * Math.pow(10, digits)) / Math.pow(10, digits) || num;
  },

  /**
   * 
   * 将数字千分位分割
   * 
   * @param {number | string} num  需要转换的数字
   * @param {string} symbol        分割的字符
   * @returns 
   */
  formatNumber(num, symbol = ',') {
    if (!objectUtils.isParseNumber(num)) return '';

    return num.toString().replace(/\d+/, function (n) {
      // 先提取整数部分
      return n.replace(/(\d)(?=(\d{3})+$)/g, ($1) => $1 + symbol);
    });
  },

  /**
    * @description: 分转元
    * @param {number|string} fen 要转换的钱，单位分
    * @return {*}
    */
  regFenToYuan(fen) {
    return this.isParseNumber(fen) && this.formatNumber(this.toDecimal(fen * 0.01)) || fen;
  },

  /**
   * @description: 元转分
   * @param {string|number} yuan:要转换的钱，单位元 
   * @return {*}
   */
  regYuanToFen(yuan) {
    if (!objectUtils.isParseNumber(yuan)) return yuan;

    let m = this.getDecimalsDigits(yuan);
    return yuan * 100 * Math.pow(10, m) / Math.pow(10, m);
  },

  /**
   * @description: 获取小数位数
   * @param {number|string} num 数字
   * @return {*}
   */
  getDecimalsDigits(num) {
    if (!objectUtils.isParseNumber(num) || Math.floor(num) === num) return 0;

    return num.toString().split('.')[1].length || 0;
  },

  /**
   * @description:  两个小数相加
   * @param {number} num1  加数1
   * @param {number} num2  加数2
   * @return {number} 相加后的数字
   */
  addTwoDecimal(num1, num2) {
    let num1Digi = this.getDecimalsDigits(num1),
      num2Digi = this.getDecimalsDigits(num2),
      maxDigi = Math.max(num1Digi, num2Digi),
      prev = convertUtils.toInterger(num1, maxDigi),
      next = convertUtils.toInterger(num2, maxDigi);

    return (prev + next) / Math.pow(10, maxDigi);
  },

  /**
   * @description: 两个数相乘
   * @param {number} num1  乘数1
   * @param {number} num2  乘数2
   * @return {number} 相乘后的数
   */
  multiplyTwoDecimal(num1, num2) {
    let num1Digi = this.getDecimalsDigits(num1),
      num2Digi = this.getDecimalsDigits(num2),
      num1Pow = Math.pow(10, num1Digi),
      num2Pow = Math.pow(10, num2Digi),
      prev = convertUtils.toInterger(num1, num1Digi),
      next = convertUtils.toInterger(num2, num2Digi);

    return prev * next / (num1Pow * num2Pow);
  },

  /**
   * @description: 前补零（str不能为空，否则会少一位TT）
   * @param {string} str 当前字符串
   * @param {number} digits   位数, 默认2位
   * @return {*}
   */
  fixZeroStart(str, digits = 2) {
    if (!numberUtils.isParseNumber(str)) return str;

    return convertUtils.toString(str, '').padStart(digits, '0').substring(0, digits);
  },

  /**
   * @description: 后补零（str不能为空，否则会少一位TT）
   * @param {string} str 当前字符串
   * @param {number} digits   位数, 默认2位
   * @return {*}
   */
  fixZeroEnd(str, digits = 2) {
    if (!numberUtils.isParseNumber(str)) return str;

    return convertUtils.toString(str, '').padEnd(digits, '0').substring(0, digits);
  },

  /**
   * @description: 将数字转成中文数字
   * @param {string|number} val
   * @return {*}
   */
  numToChinese(val) {
    if (objectUtils.isEmpty(val)) return '';
    if (!objectUtils.isNumber(val) || !objectUtils.isParseNumber(val)) return '';

    // 汉字的数字
    let cnNums = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
    // 基本单位
    let cnIntRadice = ['', '拾', '佰', '仟'];
    // 对应整数部分扩展单位
    let cnIntUnits = ['', '万', '亿', '兆'];
    // 对应小数部分单位
    let cnDecUnits = ['角', '分', '毫', '厘'];
    // 整数金额时后面跟的字符
    let cnInteger = '整';
    // 整型完以后的单位
    let cnIntLast = '元';
    // 最大处理的数字
    let maxNum = 999999999999999.9999;
    // 金额整数部分
    let IntegerNum;
    // 金额小数部分
    let DecimalNum;
    // 输出的中文金额字符串
    let chineseStr = '';
    // 正负值标记
    let Symbol = '';

    let money = convertUtils.toNumberFloat(val);
    if (money >= maxNum) {
      console.error('超出最大处理数字');
      return '';
    }

    if (money == 0) return `${cnNums[0]}${cnIntLast}${cnInteger}`;

    if (money < 0) {
      money = -money;
      Symbol = '负 ';
    }

    // 分离金额后用的数组，预定义    
    let parts = money.toString().split('.');
    if (parts.length <= 1) {
      IntegerNum = money;
      DecimalNum = '';
    } else {
      IntegerNum = parts[0];
      DecimalNum = parts[1].substr(0, 4);
    }

    if (parseInt(IntegerNum, 10) > 0) { // 获取整型部分转换
      let zeroCount = 0;
      let IntLen = IntegerNum.length;
      for (let i = 0; i < IntLen; i++) {
        let n = IntegerNum.substr(i, 1);
        let p = IntLen - i - 1;
        let q = p / 4;
        let m = p % 4;
        if (n == '0') {
          zeroCount++;
        } else {
          if (zeroCount > 0) {
            chineseStr += cnNums[0];
          }
          zeroCount = 0; // 归零
          chineseStr += cnNums[parseInt(n)] + cnIntRadice[m];
        }

        if (m == 0 && zeroCount < 4) {
          chineseStr += cnIntUnits[q];
        }
      }
      chineseStr += cnIntLast;
      // 整型部分处理完毕
    }

    if (!!DecimalNum) { // 小数部分
      let decLen = DecimalNum.length;
      for (let i = 0; i < decLen; i++) {
        let n = DecimalNum.substr(i, 1);
        if (n != '0') {
          chineseStr += cnNums[Number(n)] + cnDecUnits[i];
        }
      }
    }

    if (chineseStr == '') {
      chineseStr += cnNums[0] + cnIntLast + cnInteger;
    } else if (DecimalNum == '') {
      chineseStr += cnInteger;
    }
    chineseStr = Symbol + chineseStr;

    return chineseStr;
  },

  /**
   * 数字转中文大写
   * @param val 当前值字符串
   * @returns 返回处理后的字符串
   */
  numToChineseUppercase(val) {
    if (objectUtils.isEmpty(val)) return '';
    if (!objectUtils.isNumber(val) || !objectUtils.isParseNumber(val)) return '';

    // 单位
    let unit = '仟佰拾亿仟佰拾万仟佰拾元角分';
    // 当前内容字符串添加 2个0，为什么??
    val += '00';
    // 返回某个指定的字符串值在字符串中首次出现的位置，没有出现，则该方法返回 -1
    let lookup = val.indexOf('.'), valLen = val.length;
    // substring：不包含结束下标内容，substr：包含结束下标内容
    if (lookup >= 0) val = val.substring(0, lookup) + val.substring(lookup + 1, 2);
    // 根据内容 val 的长度，截取返回对应大写
    unit = unit.substring(unit.length - valLen);
    let v = '';
    // 循环截取拼接大写
    for (let i = 0; i < valLen; i++) {
      v += '零壹贰叁肆伍陆柒捌玖'.substring(val.substr(i, 1), 1) + unit.substring(i, 1);
    }
    // 正则处理
    v = v
      .replace(/零角零分$/, '整')
      .replace(/零[仟佰拾]/g, '零')
      .replace(/零{2,}/g, '零')
      .replace(/零([亿|万])/g, '$1')
      .replace(/零+元/, '元')
      .replace(/亿零{0,3}万/, '亿')
      .replace(/^元/, '零元');
    // 返回结果
    return v;
  }
};

// 公用的对象方法
export const objectUtils = {

  /**
   * @description: 是否是想要的数据类型 返回数据的类型
   * @param {unknown} val 
   * @param {string} type  目标类型
   * @return {*}
   */
  is(val, type) {
    return Object.prototype.toString.call(val) === `[object ${type}]`;
  },

  /**
   * @description: 是 undefined
   * @param {any} val
   * @return {*}
   */
  isUndef(val) {
    return typeof val === 'undefined';
  },

  /**
   * @description: 不是 undefined
   * @param {T} val
   * @return {*}
   */
  isNotDef(val) {
    return !this.isUndef(val);
  },

  /**
   * @description: 是 null
   * @param {unknown} val
   * @return {*}
   */
  isNull(val) {
    return val === null;
  },

  /**
   * @description: 不是空
   * @param {unknown} val
   * @return {*}
   */
  isNotNull(val) {
    return !this.isNull(val);
  },

  /**
   * @description: 是空又是undefined
   * @param {unknown} val
   * @return {*}
   */
  isNullAndUnDef(val) {
    return this.isUndef(val) && this.isNull(val);
  },

  /**
   * @description: 是空或者是 undefined
   * @param {unknown} val
   * @return {*}
   */
  isNullOrUnDef(val) {
    return this.isUndef(val) || this.isNull(val);
  },

  /**
   * 是否是 boolean 类型数据
   * 
   * @param {any} val
   */
  isBoolean(val) {
    return typeof val === 'boolean';
  },

  /**
   * 是否是 string 数据类型
   * 
   * @param {any} val
   */
  isString(val) {
    return typeof val === 'string';
  },

  /**
   * 是否是 Array
   * 
   * @param {any} val
   */
  isArray(val) {
    return val && Array.isArray(val);
  },

  /**
   * 是否是 object 不包含数组 纯 {} 对象
   * 
   * @param {any} val
   */
  isObject(val) {
    return val != null && typeof val === 'object' && !this.isArray(val);
  },

  /**
   * @description: 是否是文件对象
   * @param {any} val
   * @return {*}
   */
  isFile(val) {
    return this.is(val, 'File');
  },

  /**
   * @description: 是否是blob对象
   * @param {any} val
   * @return {*}
   */
  isBlob(val) {
    return this.is(val, 'Blob');
  },

  /**
 * @description: 是否是 FormData
 * @param {any} val
 * @return {*}
 */
  isFormData(val) {
    return typeof FormData !== 'undefined' && val instanceof FormData;
  },

  /**
   * 是否是一个函数
   * 
   * @param {any} val
   */
  isFunction(val) {
    return typeof val === 'function' && typeof val.nodeType !== 'number';
  },

  /**
   * @description: 是 Promise
   * @param {any} val
   * @return {*}
   */
  isPromise(val) {
    return this.is(val, 'Promise') && this.isObject(val) && this.isFunction(val.then) && this.isFunction(val.catch);
  },

  /**
   * 
   * 数据是否是空
   * 
   * @param {any} val
   * @return {boolean}  true 表示空  false 表示不为空
   * 
   */
  isEmpty(val) {
    if (val == null) return true;

    // 是字符串
    if (this.isString(val)) return !val.trim() || val === '[]' || val === '{}' || val === '[null]';
    // 是数组
    if (this.isArray(val)) return val.length <= 0;
    // 是 Map 或者 Set
    if (val instanceof Map || val instanceof Set) return val.size === 0;
    // 是对象
    if (this.isObject(val)) return JSON.stringify(val) === '{}';
    // 是数字
    if (numberUtils.isNumber(val)) return !Number.isFinite(val);

    return false;
  },

  /**
   * 数据 不为空
   * 
   * @param val 
   * @returns 
   */
  isNotEmpty(val) {
    return !this.isEmpty(val);
  },

  /**
   * @description: 是否是 window
   * @param {any} val
   * @return {*}
   */
  isWindow(val) {
    return typeof window !== 'undefined' && this.is(val, 'Window');
  },

  /**
   * @description: 是否是 element
   * @param {any} val
   * @return {*}
   */
  isElement(val) {
    return this.isObject(val) && !!val.tagName;
  },

  /**
   * @description: 是否是 map 对象
   * @param {unknown} val
   * @return {*}
   */
  isMap(val) {
    return this.is(val, 'Map');
  },

  /**
   * @description: 是 RegExp 对象
   * @param {unknown} val
   * @return {*}
   */
  isRegExp(val) {
    return this.is(val, 'RegExp');
  },

  /**
   * @description: 两个对象是否相似, object/Array
   * @param {any} obj1
   * @param {any} obj2
   * @return {*}
   */
  isSimilar(obj1, obj2) {
    if (this.isEmpty(obj1)) return false;
    if (this.isEmpty(obj2)) return false;

    return JSON.stringify(obj1) == JSON.stringify(obj2);
  },

  /**
   * 判断对象某个属性值是否为空
   * 
   * @param obj 
   * @param key 
   */
  isPropertyEmpty(obj, key) {
    return !this.hasProperty(obj, key) || this.isEmpty(obj[key]);
  },

  /**
   * 对象属性是否存在
   * 
   * @param obj  对象
   * @param key  对象的属性key
   */
  hasProperty(obj, key) {
    return !this.isEmpty(obj) && this.isObject(obj) && Reflect.has(obj, key);
  },

  /**
   * 去掉文字中的空格
   * 
   * 尽量 使用 string 自带的方法
   * 
   * @param {string} text
   * @returns {string}
   */
  trim(text) {
    return text?.trim() || '';
  },

  /**
   * 去掉文字开始空格
   * 
   * @param {*} text 需要去掉点空格的字符串
   * @returns 
   */
  startTrim(text) {
    return text?.startTrim() || '';
  },

  /**
   * 去掉文字结束的空格
   * 
   * @param {*} text 需要去掉点空格的字符串
   * @returns 
   */
  endTrim(text) {
    return text?.endTrim() || '';
  },

  /**
   * 深度克隆对象、数组
   * 
   * @param obj 被克隆的对象
   * @return 克隆后的对象
   */
  cloneObject(obj) {
    if (this.isEmpty(obj) || !this.isArray(obj) || !this.isObject(obj)) return obj;

    return JSON.parse(JSON.stringify(obj));
  },

  /**
   * 判断该对象/单值是否在数组中
   *
   * 不能判断对象中的某个属性
   *
   * @param {object} elem  数组中的某个元素
   * @param {Array}  arr   数组
   * @param {number} i     开始位置, 默认开始位置为 0
   * @returns {number} -1 表示不存在
   */
  inArray(elem, arr, i = 0) {
    return this.isEmpty(arr) ? -1 : [].indexOf.call(arr, elem, i);
  },

  /**
   * 将第二个数组合并到第一个内
   * 
   * @param {Array} first   第一个数组
   * @param {Array} second  第二个数组
   * @returns {Array<any>}  返回合并后的数组
   * 
   */
  merge(first, second) {
    if (!this.isArray(first) || !this.isArray(second)) return first;

    return [...first, ...second];
  },

  /**
   * @description: 两个对象深度合并
   * @param {any} src
   * @param {any} target
   * @return {*}
   */
  deepMerge(src = {}, target = {}) {
    for (let key in target) {
      src[key] = this.isObject(src[key]) ? this.deepMerge(src[key], target[key]) : target[key];
    }

    return src;
  },

  /**
   * 过滤对象中为空的属性
   *
   * @param obj
   * @returns {*}
   */
  filterEmptyProperty(obj) {
    if (!this.isObject(obj)) return obj;

    for (let key in obj) {
      if (!this.isPropertyEmpty(obj, key)) continue;

      delete obj[key];
    }
    return obj;
  },

  /**
   * 使一个值永远不会为（null | undefined）
   *
   * @param value 要处理的值
   * @param def 默认值，如果value为（null | undefined）则返回的默认值，可不传，默认为''
   */
  neverNull(value, def = '') {
    return value == null ? (this.neverNull(def, '')) : value;
  },

  /**
   * 如果值不存在就 push 进数组，反之不处理
   * 
   * @param array 要操作的数据
   * @param value 要添加的值
   * @param key 可空，如果比较的是对象，可能存在地址不一样但值实际上是一样的情况，可以传此字段判断对象中唯一的字段，例如 id。不传则直接比较实际值
   * @returns {boolean} 成功 push 返回 true，不处理返回 false
   */
  pushIfNotExist(array, value, key) {
    if (this.isEmpty(array) || this.isEmpty(value)) return false;

    let index = array.findIndex((item) => item === key || (key && item[key] === value[key]));
    if (index >= 0) return false;

    array.push(value);
    return true;
  }
};

// 正则匹配
export const regexpUtils = {

  /**
   * 是否是图片
   * 
   * @param {string} filePath 图片路径
   */
  isImage(filePath) {
    return objectUtils.isString(filePath) && /.(gif|jpg|jpeg|png|gif|jpg|png)$/.test(filePath.toLowerCase());
  },

  /**
   * 是视频文件文件
   *
   * @param {string} filePath  文件地址
   * @return
   */
  isVideoFile(filePath) {
    return objectUtils.isString(filePath) && /.(m4a|mid|ogg|mp3|mp4|xmf|wav)$/.test(filePath.toLowerCase());
  },

  /**
   * 是text文件
   *
   * @param  {string} filePath 文件名
   */
  isTextFile(filePath) {
    return objectUtils.isString(filePath) && /.(txt|java|c|cpp|py|xml|json|log|vue|js|ts)$/.test(filePath.toLowerCase());
  },

  /**
   * 是 word 文件
   *
   * @param filePath 文件名
   */
  isWordFile(filePath) {
    return objectUtils.isString(filePath) && /.(doc|docx)$/.test(filePath.toLowerCase());
  },

  /**
   * 是excel文件
   *
   * @param filePath
   */
  isExcelFile(filePath) {
    return objectUtils.isString(filePath) && /.(xls|xlsx)$/.test(filePath.toLowerCase());
  },

  /**
   * 是apk文件
   *
   * @param filePath 文件名/ 文件路径
   */
  isApkFile(filePath) {
    return objectUtils.isString(filePath) && filePath.toLowerCase().endsWith('.apk');
  },

  /**
   * 是pdf文件
   *
   * @param filePath 文件名/ 文件路径
   */
  isPdfFile(filePath) {
    return objectUtils.isString(filePath) && filePath.toLowerCase().endsWith('.pdf');
  },

  /**
   * @description: 是否是手机号 (目前只支持11位手机号)
   * @param {*}
   * @return {*}
   */
  isPhone(phone) {
    return objectUtils.isString(phone) && /^1(2|3|4|5|6|7|8|9)\d{9}$/.test(phone);
  },

  /**
   * @description: 是否是座机号码
   * @param {*}
   * @return {*}
   */
  isLandline(phone) {
    return objectUtils.isString(phone) && /^\d{3}-\d{7,8}|\d{4}-\d{7,8}$/.test(phone);
  },

  /**
   * @description: 身份证号码,包括15位和18位的
   * @param {*} val
   * @return {*}
   */
  isIDCard(val) {
    return objectUtils.isString(val) && /(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{7}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)/.test(val);
  },

  /**
   * 匹配 15 位的身份证
   * 
   * @param {Object} val
   */
  isIDCard1(val) {
    return objectUtils.isString(val) && /^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$/.test(val);
  },

  /**
   * @description:  匹配 18 位的身份证
   * @param {string} val
   * @return {*}
   */
  isIDCard2(val) {
    return objectUtils.isString(val) && /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{4}$/.test(val);
  },

  /**
   * @description:  验证是否是邮件地址
   * 
   * /^([a-zA-Z0-9._-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(s)
   * @param {string} val
   * @return {*}
   */
  isEmail(val) {
    return objectUtils.isString(val) && /\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(val);
  },

  /**
   * @description:  是否是IP地址
   * @param {string} val
   * @return {*}
   */
  isIP(val) {
    return objectUtils.isString(val) && /(\d+)\.(\d+)\.(\d+)\.(\d+)/.test(val);
  },

  /**
   * @description: 是否是正确的网站
   * /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
   * 
   * @param {string} val
   * @return {*}
   */
  isURL(val) {
    return objectUtils.isString(val) && /^(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-.,@?^=%&:/~+#]*[\w\-@?^=%&/~+#])?$/.test(val);
  },

  /**
   * 是外部链接 
   * 
   * @param {string} path
   * @returns {Boolean}
   */
  isExternal(path) {
    return objectUtils.isString(path) && /^(https?:|mailto:|tel:)/.test(path);
  },

  /**
   * @description: 正确的银行卡号
   * @param {string} val
   * @return {*}
   */
  isBankCard(val) {
    return objectUtils.isString(val) && /^[1-9]\d{9,19}$/.test(val);
  },

  /**
   * @description: 是否是正确的qq号
   * @param {string} val
   * @return {*}
   */
  isQQCard(val) {
    return objectUtils.isString(val) && /^[1-9]\d{4,11}$/.test(val);
  },

  /**
   * 是否是英文
   * 
   * @param {string} val
   */
  isEnglish(val) {
    return objectUtils.isString(val) && /^[a-zA-Z]+$/.test(val);
  },

  /**
   * 是否是 数字和英文
   * 
   * @param {string} val
   */
  isNumAndEnglish(val) {
    return objectUtils.isString(val) && /^[0-9a-zA-Z]+$/.test(val);
  },

  /**
   * 是否是中文和英文
   * 
   * @param {string} val
   */
  isChineAndEnglish(val) {
    return objectUtils.isString(val) && /^[\u4e00-\u9fa5a-zA-Z]+$/.test(val);
  },

  /**
   * 是否是中文
   * 
   * @param {string} val
   */
  isChinese(val) {
    return objectUtils.isString(val) && /[\u4E00-\u9FA5]/g.test(val);
  },

  /**
   * @description: 是否是正确的车牌号
   * @param {string} val  车牌
   * @return {*}
   */
  isNumberPlate(val) {
    return objectUtils.isString(val) && /^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[DF])|([DF]([A-HJ-NP-Z0-9])[0-9]{4})))|([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))$/.test(val);
  },

  /**
   * @description: 严重是否是正确的邮政编码
   * @param {string} val
   * @return {*}
   */
  isPostalCode(val) {
    return objectUtils.isString(val) && /^[1-9][0-9]{5}$/.test(val);
  },

  /**
  * @description: 是否事移动端
  * @param {*}
  * @return {*}
  */
  isMobile() {
    return /('phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone')/i.test(navigator?.userAgent || '');
  },

  /**
    * @description: 是服务器
    * @param {*}
    * @return {*}
    */
  isServer() {
    return typeof window === 'undefined';
  },

  /**
   * @description: 是客户端
   * @param {*}
   * @return {*}
   */
  isClient() {
    return !this.isServer();
  }
};

// 其他公用方法
export const utils = {

  /**
   * @description: 生成唯一的guid
   * @param { string } prefix  guid 前缀
   * @return { string } 返回guid
   */
  guid(prefix = '') {
    function S4() {
      return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
    }

    return (prefix || '') + (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4());
  },

  /**
   * 随机生成uuid, 默认 32 位
   * 
   * @param {number} length 字符串的长度
   * @param {string} prefix 显示的前缀
   * @param {string} chats 可选字符串区间（只会生成传入的字符串中的字符）
   * @return string 生成的字符串
   */
  uuid(length = 32, prefix = '', chats = '0123456789qwertyuioplkjhgfdsazxcvbnm') {
    let str = '';
    for (let i = 0; i < length; i++) {
      let num = this.randomNumber(0, chats.length - 1) || 0;
      str += chats[num];
    }

    return `${prefix}${str}`;
  },

  /**
   * @description: 短的 uuid 16位
   * @param {string} prefix
   * @param {string} chats
   * @return {*}
   */
  shortUuid(prefix = '', chats = '0123456789qwertyuioplkjhgfdsazxcvbnm') {
    return this.uuid(16, prefix, chats);
  },

  /**
   * 随机生成数字
   *
   * 示例：生成长度为 12 的随机数：randomNumber(12)
   * 示例：生成 3~23 之间的随机数：randomNumber(3, 23)
   *
   * @param1 最小值 | 长度
   * @param2 最大值
   * @return int 生成后的数字
   */
  randomNumber(...args) {
    if (objectUtils.isEmpty(args)) return 0;

    // 生成 最小值 到 最大值 区间的随机数
    const random = (min, max) => {
      return Math.floor(Math.random() * (max - min + 1) + min);
    };

    if (args.length <= 1) {
      let [length] = args;
      // 生成指定长度的随机数字，首位一定不是 0
      let nums = [...Array(length).keys()].map((i) => random(i ? 0 : 1, 9));
      return Number.parseInt(nums.join(''));
    }

    let [min, max] = args;
    return random(min, max);
  },

  /**
   * @description: 将表情转为字符
   * @param {string} str 需要转换的表情字符川
   * @return {*}  转换后的字符串
   */
  emojiToStr(str) {
    if (!objectUtils.isString(str)) return '';

    // 检测utf16字符正则 
    return str.replace(/[\ud800-\udbff][\udc00-\udfff]/g, function (char) {
      if (char.length != 2) return char;

      // 取出高位 
      let H = char.charCodeAt(0),
        // 取出低位 
        L = char.charCodeAt(1),
        // 转换算法 
        code = (H - 0xD800) * 0x400 + 0x10000 + L - 0xDC00;

      return `&#${code};`;
    });
  },

  /**
   * @description:  将字符转为表情
   * @param {string} str 需要转换的字符串
   * @return {*}   转换后的字符串
   */
  strToEmoji(str) {
    if (!objectUtils.isString(str)) return '';

    return str.replace(/\&#.*?;/g, function (char) {
      if (char.length != 9) return char;

      let cStr = char.match(/[0-9]+/g),
        code = Number.parseInt(cStr),
        H = Math.floor((code - 0x10000) / 0x400) + 0xD800,
        L = (code - 0x10000) % 0x400 + 0xDC00;

      return unescape('%u' + H.toString(16) + '%u' + L.toString(16));
    });
  },

  /**
   * @description:  隐藏手机号中间的数字
   * @param {string} value 手机号码
   * @return {string}  隐藏后的手机号 
   */
  phoneHidden(phone) {
    return regexpUtils.isPhone(phone) && phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') || phone;
  },

  /**
   * @description:  下划线转驼峰
   * @param {string} val
   * @return {*}
   */
  underLine2CamelCase(val) {
    return objectUtils.isString(val) && val.replace(/_([a-z])/g, (all, letter) => letter.toUpperCase()) || val;
  },

  /**
   * @description:  中划线转驼峰
   * @param {string} val
   * @return {*}
   */
  lineThrough2CamelCase(val) {
    return objectUtils.isString(val) && val.replace(/-([a-z])/g, (all, letter) => letter.toUpperCase()) || val;
  },

  /**
   * @description: 首字母小写
   * @param {string} val
   * @return {*}
   */
  lowercaseFirstLetter(val) {
    return objectUtils.isString(val) && val.charAt(0).toLowerCase() + val.substring(1);
  },

  /**
   * @description: 首字母大写
   * @param {string} val
   * @return {*}
   */
  uppercaseFirstLetterF(val) {
    return objectUtils.isString(val) && val.charAt(0).toUpperCase() + val.substring(1);
  },

  /**
   * 简单实现防抖方法
   *
   * 防抖(debounce)函数在第一次触发给定的函数时，不立即执行函数，而是给出一个期限值(delay)，比如100ms。
   * 如果100ms内再次执行函数，就重新开始计时，直到计时结束后再真正执行函数。
   * 这样做的好处是如果短时间内大量触发同一事件，只会执行一次函数。
   *
   * @param  { Function } fn    要防抖的函数
   * @param  { number} delay 防抖的毫秒数
   * @returns {Function}
   */
  simpleDebounce(fn = () => { }, delay = 100) {
    let timer = null;

    return function () {
      if (timer) clearTimeout(timer);

      timer = setTimeout(() => {
        // @ts-ignore
        fn.apply(this, arguments);
      }, delay);
    };
  },

  /**
   * @description 函数节流: 每隔一段时间，只执行一次函数
   * @param { Function } fn 需要延迟执行的函数
   * @param { number } threshold
   * @param scope
   */
  throttle(fn = () => { }, threshold = 300, scope) {
    let last = 0, deferTimer;

    return function () {
      // @ts-ignore
      const context = scope || this,
        nt = dateUtils.currentTimeMillis(),
        args = arguments;

      if (last && nt < last + threshold) {
        clearTimeout(deferTimer);
        deferTimer = setTimeout(() => {
          last = nt;
          fn.apply(context, args);
        }, threshold);
      } else {
        last = nt;
        fn.apply(context, args);
      }
    };
  },

  /**
   * @description 距离换算
   * @param {number} distance 原距离
   * @returns {string} 新距离
   */
  getDistance(distance) {
    if (!objectUtils.isParseNumber(distance)) return '';
    let isKM = distance >= 1000;

    distance = convertUtils.toNumberFloat(distance);
    return `${numberUtils.toDecimal(distance / (isKM ? 1000 : 1))}${!isKM ? '米' : '公里'}`;
  }
};
